#!/usr/bin/env python
""" AnyBackup is a windows specific backup program to keep multiple volumes in sync with multiple volumes
    Copyright (C) 2011  Andrew Hopkinson

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.

    Exported Classes:

    StoppableThread -- a thread which can be stopped
    myFrame -- the main GUI component of AnyBackup
    """
from multiprocessing import Queue
import os
import Queue as realQueue
import logging
import logging.handlers
import subprocess
import functools
import wx
from sql import getHomedir
import wx.lib.agw.aui as aui
import wx.lib.agw.toasterbox as TB
import wx.lib.mixins.listctrl as listmix
from wx.lib.agw.multidirdialog import MultiDirDialog
import sys
from FileUtil import FileUtil
import DriveUtil
from easyListCtrl import easyListCtrl
from dirTree import dirTree
from backupManager import backupManager
from listdialog import listdialog, manageDirDialog, remoteDialog, SearchDialog, DriveInfo, PreferenceDialog
from ScrollingProgressDialog import ScrollingProgressDialog, FileProgressDialog, ChkdskDialog
from mySplash import mySplash
from enum import AEnums
from threading import Thread, Event
import pythoncom
import csv
import traceback

logger = logging.getLogger('AnyBackup')


class LoggerWriter:
    def __init__(self, level):
        self.level = level

    def write(self, message):
        message = message.strip().strip('\n')
        if message:
            logger.log(self.level, message)


sys.stdout = LoggerWriter(logging.INFO)
sys.stderr = LoggerWriter(logging.ERROR)


class AnyThread(Thread):
    def __init__(self, target=None, bucket=None, title='Error'):
        self.target = target
        Thread.__init__(self)
        self.bucket = bucket
        self.title = title

    # noinspection PyBroadException
    def run(self):
        pythoncom.CoInitialize()
        try:
            self.target()
        except:
            if self.bucket:
                self.bucket.put((self.title, traceback.format_exc()))
        finally:
            pythoncom.CoUninitialize()


class AnyPanel(wx.Panel, listmix.ColumnSorterMixin):
    def GetListCtrl(self):
        self.itemDataMap = self.list_ctrl.getItemDataMap()
        return self.list_ctrl.getListCtrl()

    def __ColumnSorter(self, key1, key2):
        print key1, key2
        return super(AnyPanel, self).__ColumnSorter(key1, key2)

class AnyFrame(wx.Frame, AnyPanel):
    """ the main GUI component of AnyBackup, the whole of the GUI """
    MISSING = 9
    _maxResults = 1000

    def __init__(self, parent, ID, title, position, size, guest=None):
        if not guest:
            title = title + ' ' + backupManager.version
        wx.Frame.__init__(self, parent, ID, title, position, size)
        self.hd = getHomedir()
        self.logger = logger
        self.guest = guest
        self.formatter = logging.Formatter(fmt='%(asctime)s - %(levelname)s - %(funcName)s - %(message)s')
        self.handler = logging.handlers.TimedRotatingFileHandler(
            os.path.join(self.hd, 'AnyBackup.log'), when='D', interval=1, backupCount=5)
        self.handler.setFormatter(self.formatter)
        self.logger.addHandler(self.handler)
        self.logger.setLevel(logging.DEBUG)
        self.bucket = realQueue.Queue()
        self.logger.info('Starting AnyBackup')
        self.buManager = backupManager(guest=guest)
        self.messagingQueue = Queue()
        self.errorQueue = Queue()
        self.errorAnswerQueue = Queue()
        splash = mySplash()
        splash.show()

        splash.setStatusText('Building GUI')
        self.mgr = aui.AuiManager()
        self.mgr.SetManagedWindow(self)
        self.bottomPanel = wx.Panel(self, -1, size=(450, 150))
        self.drvPanel = wx.Panel(self, -1, size=(400, 300))
        self.resultPanel = resultPanel = AnyPanel(self.bottomPanel)
        drvSelPanel = wx.Panel(self.drvPanel)
        drvListPanel = AnyPanel(self.drvPanel)

        boxBottom = wx.BoxSizer(wx.HORIZONTAL)
        driveBox = wx.BoxSizer(wx.VERTICAL)
        drvSelBox = wx.BoxSizer(wx.HORIZONTAL)
        drvListBox = wx.BoxSizer(wx.HORIZONTAL)
        resultBox = wx.FlexGridSizer(rows=2, cols=1)
        self.saving = False
        self.unsaved = False
        self.ids = {}
        self.sdialog = {}
        self.thread = None
        self.menu = None
        self.cursor = None
        self.running = False
        keys = ['cnLabel', 'drives', 'files', 'exit', 'addMenu',
                'backupMenu', 'removeMenu', 'toBackupMenu',
                'oldFilesMenu', 'results', 'statusBar',
                'editListMenu', 'save', 'contents', 'about',
                'refreshBackup', 'refreshContent', 'reset',
                'refreshSelected', 'search', 'dirTree',
                'dirLabel', 'progressBar', 'clear', 'restoreMenu',
                'resultPage', 'toggleMenu', 'tb_add', 'tb_remove',
                'tb_refresh', 'tb_lock', 'tb_backup', 'tb_search',
                'tb_switch', 'duplicates', 'timer', 'refreshButton',
                'sets', 'addset', 'removeset', 'refreshAll', 'copy',
                'export', 'guest', 'prefs', 'popupabout', 'popuprefresh',
                'popupremove', 'popupdir', 'popupchkdsk', 'popupremote',
                'popuplock']
        for key in keys:
            self.ids[key] = wx.NewId()
        self.CreateStatusBar()
        self.GetStatusBar().SetFieldsCount(3)
        self.fileUtilObj = FileUtil()
        self.cnLegend = wx.StaticText(drvSelPanel, -1, 'Content Drive')
        self.cnLegend.SetFont(wx.Font(7, wx.DEFAULT, wx.NORMAL, wx.BOLD))
        self.cnLegend2 = wx.StaticText(drvSelPanel, -1, 'Backup Drive')
        self.cnLegend2.SetFont(wx.Font(7, wx.DEFAULT, wx.NORMAL, wx.NORMAL))
        self.refreshButton = wx.BitmapButton(drvSelPanel, self.ids['refreshButton'],
                                             wx.Bitmap("icons/refresh.png", wx.BITMAP_TYPE_PNG))
        self.refreshButton.SetToolTip(wx.ToolTip('Refresh drive list, will update displayed drive connection status.'))
        self.drives = easyListCtrl(drvListPanel, self.ids['drives'], self.fileUtilObj, mode=easyListCtrl.DRIVE,
                                   single=False)
        drvListPanel.list_ctrl = self.drives
        listmix.ColumnSorterMixin.__init__(drvListPanel, 4)
        self.drives.SetImageList(self.fileUtilObj.getDriveList(), wx.IMAGE_LIST_SMALL)
        self.dirTreeObj = dirTree(self, self.ids['dirTree'], self.fileUtilObj)
        self.dirTreeObj.SetImageList(self.fileUtilObj.getImageList())
        self.list_ctrl = self.contents = easyListCtrl(self, self.ids['contents'], self.fileUtilObj, mode=easyListCtrl.FILE, single=False)
        self.contents.SetImageList(self.fileUtilObj.getImageList(), wx.IMAGE_LIST_SMALL)
        listmix.ColumnSorterMixin.__init__(self, 3)
        self.results = easyListCtrl(resultPanel, self.ids['results'], self.fileUtilObj, mode=easyListCtrl.FILE,
                                    single=False)
        resultPanel.list_ctrl = self.results
        listmix.ColumnSorterMixin.__init__(resultPanel, 3)
        self.results.SetImageList(self.fileUtilObj.getImageList(), wx.IMAGE_LIST_SMALL)
        self.resultPage = wx.ComboBox(resultPanel, self.ids['resultPage'], '', wx.DefaultPosition, wx.DefaultSize, [],
                                      style=wx.CB_READONLY)
        resultBox.Add(self.resultPage, 0, wx.LEFT | wx.TOP, 10)
        resultBox.Add(self.results.getListCtrl(), 1, wx.EXPAND | wx.ALL, 10)
        resultBox.AddGrowableRow(1)
        resultBox.AddGrowableCol(0)
        resultPanel.SetSizer(resultBox)
        drvSelBox.Add(self.cnLegend, 0, wx.LEFT | wx.TOP, 8)
        drvSelBox.Add(self.cnLegend2, 0, wx.LEFT | wx.TOP, 8)
        drvSelBox.Add(self.refreshButton, 0, wx.LEFT, 8)
        drvSelPanel.SetSizer(drvSelBox)
        drvListBox.Add(self.drives.getListCtrl(), 1, wx.EXPAND | wx.ALL, 5)
        drvListPanel.SetSizer(drvListBox)
        driveBox.Add(drvSelPanel, 0)
        driveBox.Add(drvListPanel, 1, wx.EXPAND | wx.ALL)
        self.drvPanel.SetSizer(driveBox)
        boxBottom.Add(resultPanel, 1, wx.EXPAND | wx.ALL, 0)
        self.bottomPanel.SetSizer(boxBottom)
        self.progress = None
        self.Centre()
        self.buManager.setMessagingQueue(self.messagingQueue)
        self.buManager.setErrorQueue(self.errorQueue)
        self.buManager.setErrorAnswerQueue(self.errorAnswerQueue)
        loadResult = None
        splash.setStatusText('Populating Drives')
        self.drives.populateDrives(self.getDrives())
        splash.setStatusText('Building AUI Panels')
        self.mgr.SetDockSizeConstraint(.5, .5)
        self.mgr.AddPane(self.bottomPanel,
                         aui.AuiPaneInfo().Name('Results').Caption('Results').Bottom().Layer(1).MaximizeButton(
                             True).CloseButton(False))
        self.mgr.AddPane(self.drvPanel,
                         aui.AuiPaneInfo().Name('Drives').Caption('Content and Backup Drives').Left().Layer(
                             1).MaximizeButton(True).CloseButton(False))
        self.mgr.AddPane(self.contents.getListCtrl(), aui.AuiPaneInfo().Name('File View').Caption(
            'Files in selected directory').Center().MaximizeButton(True).CloseButton(False))
        self.mgr.AddPane(self.dirTreeObj, aui.AuiPaneInfo().Name('Directory View').Caption(
            'Directory view of selected drive').Left().MaximizeButton(True).CloseButton(False))
        self.mgr.Update()
        toolbar = aui.AuiToolBar(self, -1, wx.DefaultPosition, wx.DefaultSize,
                                 agwStyle=aui.AUI_TB_DEFAULT_STYLE | aui.AUI_TB_OVERFLOW | aui.AUI_TB_TEXT |
                                 aui.AUI_TB_HORZ_TEXT)
        toolbar.SetToolBitmapSize(wx.Size(16, 16))
        tb_add = self.getBitmap('icons/add-drive.png')
        self.tb_add_small = self.getBitmap('icons/add-drive-small.png')
        self.tb_remove_small = self.getBitmap('icons/remove-drive-small.png')
        tb_remove = self.getBitmap('icons/remove-drive.png')
        tb_refresh = self.getBitmap('icons/refresh-big.png')
        self.tb_refresh_small = self.getBitmap('icons/refresh-small.png')
        tb_lock = self.getBitmap('icons/drivelock_icon.png')
        self.tb_lock_small = self.getBitmap('icons/drivelock_icon_small.png')
        tb_backup = self.getBitmap('icons/backup-icon.png')
        tb_backup_small = self.getBitmap('icons/backup-small.png')
        tb_search = self.getBitmap('icons/search.png')
        tb_search_small = self.getBitmap('icons/search-small.png')
        self.tb_explorer = self.getBitmap('icons/explorer.png')
        self.tb_find = self.getBitmap('icons/find.png')
        self.tb_content = self.getBitmap('icons/content.png')
        self.tb_dirs = self.getBitmap('icons/dirs.png')
        tb_new = self.getBitmap('icons/new.png')
        tb_old = self.getBitmap('icons/old.png')
        tb_clear = self.getBitmap('icons/clear.png')
        tb_restore = self.getBitmap('icons/restore.png')
        tb_edit = self.getBitmap('icons/edit-list.png')
        tb_add_set = self.getBitmap('icons/add-set.png')
        tb_duplicate = self.getBitmap('icons/duplicates.png')
        tb_remove_set = self.getBitmap('icons/remove-set.png')
        tb_exit = self.getBitmap('icons/exit.png')
        tb_about = self.getBitmap('icons/about.png')
        tb_export = self.getBitmap('icons/drive_go.png')
        tb_import = self.getBitmap('icons/drive_user.png')
        self.tb_driveinfo = self.getBitmap('icons/drive_info.png')
        self.tb_copy = self.getBitmap('icons/copy.png')
        self.tb_export = self.getBitmap('icons/table_export.png')
        self.tb_drive = self.getBitmap('icons/drive.png')
        self.tb_remote = self.getBitmap('icons/network.png')
        tb_settings = self.getBitmap('icons/setting.png')
        self.tb_chkdsk = self.getBitmap('icons/chkdsk.png')
        if not self.guest:
            toolbar.AddSimpleTool(self.ids['tb_add'], 'Add Drive', tb_add,
                                  'Add a new content or backup drive to the current backup set.')
            toolbar.SetToolDropDown(self.ids['tb_add'], True)
            toolbar.AddSimpleTool(self.ids['tb_remove'], 'Remove Drive', tb_remove,
                                  'Remove select drive from the current backup set.')
            toolbar.AddSimpleTool(self.ids['tb_refresh'], 'Refresh Drive(s)', tb_refresh,
                                  'Reread and index files on all selected drives.')
            toolbar.AddSimpleTool(self.ids['tb_lock'], 'Un/Lock', tb_lock,
                                  'No file operations are performed on a locked backup drive.')
            toolbar.AddSimpleTool(self.ids['tb_backup'], 'Backup', tb_backup,
                                  'Backup new files and remove old files, you can preview what' +
                                  ' files are new and old from the edit menu.')
        toolbar.AddSimpleTool(self.ids['tb_search'], 'Search', tb_search,
                              'Search for files and directories in the current backup set.')
        if not self.guest:
            self.sets = wx.ComboBox(toolbar, self.ids['sets'], choices=self.buManager.getSets(),
                                    value=self.buManager._setName, style=wx.CB_READONLY)
            toolbar.AddSeparator()
            toolbar.AddLabel(-1, 'Backup Set: ', 55)
            toolbar.AddControl(self.sets)
        self.mgr.AddPane(toolbar, aui.AuiPaneInfo().ToolbarPane().Top().CloseButton(False))
        self.mgr.Update()
        splash.setStatusText('Building Menus')
        file_menu = wx.Menu()
        if not self.guest:
            file_menu_entries = [[self.ids['search'], "Search\tCtrl-F", "Search", tb_search_small], ['-'],
                                 [self.ids['prefs'], 'Preferences', 'Changes AnyBackup preferences', tb_settings],
                                 ['-'],
                                 [self.ids['guest'], 'Guest View', 'Open An Exported View', tb_import],
                                 [self.ids['export'], "Export", "Export AnyBackup View", tb_export], ['-'],
                                 [self.ids['exit'], "E&xit\tCtrl-X", "Exit", tb_exit]]
        else:
            file_menu_entries = [[self.ids['search'], "Search\tCtrl-F", "Search", tb_search_small]]
        edit_menu = wx.Menu()
        if not self.guest:
            edit_menu_entries = [[self.ids['clear'], "&Clear All Lists\tCtrl-C", "Clear All Lists", tb_clear], ['-'],
                                 [self.ids['copy'], "Copy To", "Copy To", self.tb_copy], ['-'],
                                 [self.ids['addMenu'], "&Add Drive\tCtrl-A", "Add Drive", self.tb_add_small],
                                 [self.ids['removeMenu'], "&Remove Drive\tCtrl-R", "Remove Drive",
                                  self.tb_remove_small],
                                 ['-'], [self.ids['addset'], "Add Set", "Add Set", tb_add_set],
                                 [self.ids['removeset'], "Remove Set", "Remove Set", tb_remove_set], ['-'],
                                 [self.ids['editListMenu'], "&Edit Lists\tCtrl-E", "Edit Lists", tb_edit], ['-'],
                                 [self.ids['toBackupMenu'], "&Preview New Files\tCtrl-T", "Files to backup.", tb_new],
                                 [self.ids['oldFilesMenu'], "&Preview Old Files\tCtrl-O", "Files to delete/restore.",
                                  tb_old],
                                 [self.ids['backupMenu'], "&Backup\tCtrl-B", "Backup Now", tb_backup_small],
                                 [self.ids['restoreMenu'], "Restore", "Restore Now", tb_restore],
                                 [self.ids['duplicates'], "&Duplicate Files\tCtrl-D", "Duplicates Files", tb_duplicate]]
        else:
            edit_menu_entries = [[self.ids['clear'], "&Clear All Lists\tCtrl-C", "Clear All Lists", tb_clear], ['-'],
                                 [self.ids['copy'], "Copy To", "Copy To", self.tb_copy], ['-']]
        if not self.guest:
            refresh_menu = wx.Menu()
            refresh_menu_entries = [
                [self.ids['refreshAll'], "Refresh All Drives", "Refresh All Drives", self.tb_refresh_small],
                [self.ids['refreshBackup'], "Refresh Backup Drives", "Refresh Backup Drives", self.tb_refresh_small],
                [self.ids['refreshContent'], "Refresh Content Drives", "Refresh Content Drives", self.tb_refresh_small],
                [self.ids['refreshSelected'], "Refresh Selected Drives", "Refresh Selected Drive",
                 self.tb_refresh_small]]
        else:
            refresh_menu = None
            refresh_menu_entries = []
        help_menu = wx.Menu()
        help_menu_entries = [[self.ids['about'], 'About', 'About', tb_about]]
        self.buildMenu(file_menu, file_menu_entries)
        self.buildMenu(edit_menu, edit_menu_entries)
        if not self.guest:
            self.buildMenu(refresh_menu, refresh_menu_entries)
        self.buildMenu(help_menu, help_menu_entries)
        menuBar = wx.MenuBar()
        menuBar.Append(file_menu, '&File')
        menuBar.Append(edit_menu, '&Edit')
        if not self.guest:
            menuBar.Append(refresh_menu, '&Refresh')
        menuBar.Append(help_menu, '&Help')
        self.SetMenuBar(menuBar)
        self.SetStatusText("Welcome to AnyBackup!", 1)
        if not self.guest:
            self.chkDskNag()
        splash.setStatusText('Creating Events')
        wx.EVT_LIST_ITEM_SELECTED(self, self.ids['drives'], self.driveClicked)
        wx.EVT_LIST_ITEM_ACTIVATED(self, self.ids['contents'], self.fileClicked)
        wx.EVT_LIST_ITEM_RIGHT_CLICK(self, self.ids['contents'], self.contentRightClick)
        wx.EVT_LIST_ITEM_RIGHT_CLICK(self, self.ids['results'], self.resultsRightClick)
        wx.EVT_LIST_ITEM_RIGHT_CLICK(self, self.ids['drives'], self.driveRightClicked)
        wx.EVT_LIST_ITEM_ACTIVATED(self, self.ids['results'], self.resultsActivated)
        wx.EVT_COMBOBOX(self, self.ids['resultPage'], self.resultPageChanged)
        wx.EVT_MENU(self, self.ids['removeMenu'], self.guiRemoveDrive)
        wx.EVT_MENU(self, self.ids['addset'], self.addSet)
        wx.EVT_MENU(self, self.ids['removeset'], self.removeSet)
        wx.EVT_MENU(self, self.ids['addMenu'], self.guiAddDrive)
        wx.EVT_MENU(self, self.ids['copy'], self.copyTo)
        wx.EVT_MENU(self, self.ids['toBackupMenu'], self.guiToBackup)
        wx.EVT_MENU(self, self.ids['oldFilesMenu'], self.guiOldFiles)
        wx.EVT_MENU(self, self.ids['duplicates'], self.guiDuplicates)
        wx.EVT_MENU(self, self.ids['backupMenu'], self.guiBackup)
        wx.EVT_MENU(self, self.ids['restoreMenu'], self.guiRestore)
        wx.EVT_MENU(self, self.ids['exit'], self.exit)
        wx.EVT_MENU(self, self.ids['editListMenu'], self.editLists)
        wx.EVT_MENU(self, self.ids['refreshAll'], self.refreshAll)
        wx.EVT_MENU(self, self.ids['refreshBackup'], self.refreshBackup)
        wx.EVT_MENU(self, self.ids['refreshContent'], self.refreshContent)
        wx.EVT_MENU(self, self.ids['refreshSelected'], self.refreshSelected)
        wx.EVT_MENU(self, self.ids['search'], self.search)
        wx.EVT_MENU(self, self.ids['clear'], self.clear)
        wx.EVT_MENU(self, self.ids['about'], self.about)
        wx.EVT_MENU(self, self.ids['export'], self.export)
        wx.EVT_MENU(self, self.ids['guest'], self.viewGuest)
        wx.EVT_MENU(self, self.ids['prefs'], self.setPrefs)
        wx.EVT_TOOL(self, self.ids['tb_add'], self.createAddDriveMenu)
        wx.EVT_TOOL(self, self.ids['tb_remove'], self.guiRemoveDrive)
        wx.EVT_TOOL(self, self.ids['tb_refresh'], self.refreshSelected)
        wx.EVT_TOOL(self, self.ids['tb_lock'], self.toggleLock)
        wx.EVT_TOOL(self, self.ids['tb_backup'], self.guiBackup)
        wx.EVT_TOOL(self, self.ids['tb_search'], self.search)
        self.Bind(wx.EVT_MENU_HIGHLIGHT, self.popupHighlight)
        self.Bind(wx.EVT_MENU_CLOSE, self.popupDone)
        wx.EVT_BUTTON(self, self.ids['refreshButton'], self.refreshDriveList)
        if not self.guest:
            wx.EVT_CLOSE(self, self.exit)
        wx.EVT_TREE_SEL_CHANGED(self, self.ids['dirTree'], self.treeClicked)
        wx.EVT_TREE_ITEM_EXPANDING(self, self.ids['dirTree'], self.treeExpanded)
        wx.EVT_COMBOBOX(self, self.ids['sets'], self.setChange)
        self.buildContentMenu()
        self.buildResultMenu()
        self.SetIcon(self.fileUtilObj.getWindowIcon())
        splash.close()
        self.GetStatusBar().SetStatusWidths([-1, -4, 150])
        self.SetStatusText("Backup Mode: " + self.buManager.backupMode, 2)
        self.timer = wx.Timer(self, self.ids['timer'])
        self.timer.Start(100)
        wx.EVT_TIMER(self, self.ids['timer'], self.readQueues)
        if loadResult == backupManager.INVALIDCONFIG:
            self.SetStatusText("Invalid configuration file, using defaults!", 1)
            configDialog = wx.MessageDialog(None, 'Check AnyBackup.cfg, invalid options defined.', 'Config Error',
                                            wx.OK | wx.ICON_ERROR)
            configDialog.ShowModal()
        self.resultPage.Hide()
        self.resultPanel.Layout()

    # noinspection PyUnusedLocal
    def setPrefs(self, evt):
        dlg = PreferenceDialog(self, self.hd)
        if not dlg.ShowModal() == wx.ID_OK:
            return
        config = dlg.getConfig()
        self.buManager.backupMode = config.get('anybackup', 'drive-selection')
        self.buManager.pyroPort = int(config.get('anybackup', 'pyro-port'))
        self.buManager.nagLength = int(config.get('anybackup', 'nag-length'))
        self.SetStatusText("Backup Mode: " + self.buManager.backupMode, 2)
        self.chkDskNag()

    def getBitmap(self, image):
        return wx.Bitmap(image, wx.BITMAP_TYPE_PNG)

    # noinspection PyUnusedLocal
    def setChange(self, evt=None):
        self.buManager.setName(self.sets.GetValue())
        self.refreshDrives()

    # noinspection PyUnusedLocal
    def export(self, evt):
        if self.running:
            return
        choices = [AEnums.BACKUP, AEnums.CONTENT]
        choice = wx.MultiChoiceDialog(self, 'What type of drives should be exported?', '', choices)
        if not choice.ShowModal():
            return
        dType = [choices[x] for x in choice.GetSelections()]
        if not dType:
            return
        fileDlg = wx.FileDialog(self, 'Export AnyBackup', style=wx.FD_SAVE)
        if fileDlg.ShowModal() != wx.ID_OK:
            return
        path = os.path.join(fileDlg.GetDirectory(), fileDlg.GetFilename())
        self.runInThread("Exporting AnyBackup data...", 'Exporting',
                         self.threadedExport, fileType=False, kill=False, fileDisp=False,
                         args=(dType, path))

    def threadedExport(self, dType, path):
        self.buManager.export(dType, path)
        wx.CallAfter(self.exportDone)

    def exportDone(self):
        self.idle()
        self.SetStatusText('Export finished!', 1)
        self.thread.join()

    # noinspection PyUnusedLocal
    def viewGuest(self, evt):
        fileDlg = wx.FileDialog(self, 'Select AnyBackup Guest View', style=wx.FD_OPEN)
        if fileDlg.ShowModal() != wx.ID_OK:
            return
        path = os.path.join(fileDlg.GetDirectory(), fileDlg.GetFilename())
        fr = AnyFrame(self, -1, 'AnyBackup {0} - Guest View - {1}'.format(
            self.buManager.version, path), wx.DefaultPosition, wx.Size(900, 700), guest=path)
        fr.Show()

    # noinspection PyUnusedLocal
    def addSet(self, evt):
        d = wx.TextEntryDialog(self, 'What is the set name?', 'New Set', '')
        if d.ShowModal() == wx.ID_OK:
            answer = d.GetValue()
            if answer not in self.buManager.getSets():
                self.buManager.addSet(answer)
                self.resetSets()

    # noinspection PyUnusedLocal
    def resetSets(self, evt=None):
        self.sets.Clear()
        [self.sets.Append(x) for x in self.buManager.getSets()]
        self.sets.SetValue(self.buManager._setName)

    # noinspection PyUnusedLocal
    def removeSet(self, evt):
        backupSet = self.sets.GetValue()
        if len(self.buManager.getSets()) == 1:
            wx.MessageDialog(self, 'Cannot remove only set!', 'Error', style=wx.ICON_EXCLAMATION | wx.OK).ShowModal()
            return
        d = wx.MessageDialog(self, 'Are you sure you want to delete set {0}?'.format(backupSet), 'Confirm',
                             style=wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION)
        if d.ShowModal() == wx.ID_YES:
            self.buManager.removeSet(backupSet)
            self.resetSets()

    def getPosition(self):
        return self.ScreenToClient(wx.GetMousePosition())

    # noinspection PyUnusedLocal
    def contentRightClick(self, evt):
        self.PopupMenu(self.contentMenu, self.getPosition())

    # noinspection PyUnusedLocal
    def resultsRightClick(self, evt):
        self.PopupMenu(self.resultMenu, self.getPosition())

    # noinspection PyUnusedLocal
    def resultsActivated(self, evt):
        fileRef = self.results.GetSelectedData()
        if isinstance(fileRef, list):
            if not any(fileRef):
                return
        if not fileRef:
            return
        self.showInExplorer(fileRef)

    def buildContentMenu(self):
        self.contentMenu = wx.Menu()
        showInExplorer = wx.MenuItem(self.contentMenu, -1, 'Show In Explorer')
        showInExplorer.SetBitmap(self.tb_explorer)
        findAllCopies = wx.MenuItem(self.contentMenu, -1, 'Find All Copies')
        findAllCopies.SetBitmap(self.tb_find)
        self.contentMenu.AppendItem(showInExplorer)
        self.contentMenu.AppendItem(findAllCopies)
        self.contentMenu.Bind(wx.EVT_MENU, self.contentShowInExplorer, showInExplorer)
        self.contentMenu.Bind(wx.EVT_MENU, self.findAllCopies, findAllCopies)

    def buildResultMenu(self):
        self.resultMenu = wx.Menu()
        showInExplorer = wx.MenuItem(self.resultMenu, -1, 'Show In Explorer')
        showInExplorer.SetBitmap(self.tb_explorer)
        showInContent = wx.MenuItem(self.resultMenu, -1, 'Show In Content Panel')
        showInContent.SetBitmap(self.tb_content)
        copyTo = wx.MenuItem(self.resultMenu, -1, 'Copy To...')
        copyTo.SetBitmap(self.tb_copy)
        exportCsv = wx.MenuItem(self.resultMenu, -1, 'Export CSV')
        exportCsv.SetBitmap(self.tb_export)
        self.resultMenu.AppendItem(showInExplorer)
        self.resultMenu.AppendItem(showInContent)
        self.resultMenu.AppendItem(copyTo)
        self.resultMenu.AppendItem(exportCsv)
        self.resultMenu.Bind(wx.EVT_MENU, self.resultShowInExplorer, showInExplorer)
        self.resultMenu.Bind(wx.EVT_MENU, self.resultFileClicked, showInContent)
        self.resultMenu.Bind(wx.EVT_MENU, self.copyTo, copyTo)
        self.resultMenu.Bind(wx.EVT_MENU, self.exportCsv, exportCsv)

    # noinspection PyUnusedLocal
    def resultShowInExplorer(self, evt):
        fileRef = self.results.GetSelectedData()
        if all([type(x) == dict for x in fileRef]):
            self.showInExplorer(fileRef)

    # noinspection PyUnusedLocal
    def contentShowInExplorer(self, evt):
        fileRef = self.contents.GetSelectedData()
        self.showInExplorer(fileRef)

    def findAllCopies(self, evt):
        fileRef = self.contents.GetSelectedData()
        if fileRef:
            fileRef = fileRef[0]
        else:
            return

        if self.running:
            return

        regex = {
            AEnums.DRIVE: None,
            AEnums.DRIVES: AEnums.ALL,
            AEnums.TYPE: AEnums.FILE,
            AEnums.QUERY: '%{0}%'.format(fileRef[AEnums.NAME]),
        }
        self.runInThread("Searching for " + regex[AEnums.QUERY] + "...", 'Searching',
                         self.threadedSearch, fileType=False, kill=False, args=(regex,))

    def showInExplorer(self, fileRef):
        if type(fileRef) == list:
            fileRef = fileRef[0]
        if type(fileRef) == str:
            return
        path = self.buManager.getFullPath(fileRef)
        if path:
            if os.path.exists(path):
                subprocess.Popen(r'explorer /select,"{0}"'.format(path))
        else:
            self.SetStatusText('{0}-{1} is not hooked up!'.format(fileRef[AEnums.VOLNAME], fileRef[AEnums.SERIAL]), 1)

    def buildMenu(self, menu, entries):
        for entry in entries:
            if entry[0] == '-':
                menu.AppendSeparator()
            else:
                mi = wx.MenuItem(menu, entry[0], entry[1], entry[2])
                if len(entry) > 3:
                    mi.SetBitmap(entry[3])
                menu.AppendItem(mi)

    # noinspection PyUnusedLocal
    def about(self, evt):
        info = wx.AboutDialogInfo()
        info.SetIcon(wx.Icon("icons/windowlogo.png", wx.BITMAP_TYPE_PNG))
        info.SetName('AnyBackup')
        info.SetVersion(self.buManager.version)
        info.SetDescription('''AnyBackup is designed to fill the void of backing up one
or more large volumes to several smaller volumes.
(Though in reality it can backup any number / sized
volumes to any number / sized volumes.) These smaller volumes
need not be persistent or always connected (that is,
AnyBackup will recognize the drives even if
their drive letters change).''')
        info.SetCopyright('(C) 2013 Andrew Hopkinson')
        info.SetWebSite('http://code.google.com/p/anybackup')
        info.SetLicence('''This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.''')
        info.AddDeveloper('Andrew Hopkinson')
        info.AddDocWriter('Andrew Hopkinson')
        wx.AboutBox(info)

    # noinspection PyUnusedLocal
    def readQueues(self, evt):
        self.readStatusQueue()
        self.readErrorQueue()

    # noinspection PyUnusedLocal
    def refreshDriveList(self, evt):
        self.drives.DeleteAllItems()
        self.drives.populateDrives(self.getDrives())

    def readStatusQueue(self):
        while not self.messagingQueue.empty():
            message = self.messagingQueue.get_nowait()
            if message:
                self.SetStatusText(unicode(message).encode('utf-8'), 1)

    def readErrorQueue(self):
        while not self.errorQueue.empty():
            message = self.errorQueue.get_nowait()
            if message:
                self.errorAnswerQueue.put(self.missingDrive(message), 1)

    def createAddDriveMenu(self, evt):
        tb = evt.GetEventObject()
        tb.SetToolSticky(evt.GetId(), True)
        menuPopup = wx.Menu()
        tb_add = wx.ArtProvider.GetBitmap(wx.ART_HARDDISK, wx.ART_OTHER, wx.Size(16, 16))
        m1 = wx.MenuItem(menuPopup, -1, 'Add Backup Drive')
        m1.SetBitmap(tb_add)
        menuPopup.AppendItem(m1)

        m2 = wx.MenuItem(menuPopup, -1, 'Add Content Drive')
        m2.SetBitmap(tb_add)
        menuPopup.AppendItem(m2)

        rect = tb.GetToolRect(evt.GetId())
        pt = tb.ClientToScreen(rect.GetBottomLeft())
        pt = self.ScreenToClient(pt)
        self.Bind(wx.EVT_MENU, self.guiAddBackupDrive, m1)
        self.Bind(wx.EVT_MENU, self.guiAddContentDrive, m2)
        self.PopupMenu(menuPopup, pt)
        tb.SetToolSticky(evt.GetId(), False)

    # noinspection PyUnusedLocal
    def driveRightClicked(self, evt):
        self.menu = self.createMenu()
        self.PopupMenu(self.menu)
        self.menu.Destroy()
        self.menu = None

    def isBackupMode(self):
        driveRefs = self.drives.GetSelectedData()
        return self.buManager.isBackupMode(driveRefs)

    def isContentMode(self):
        driveRefs = self.drives.GetSelectedData()
        return not self.buManager.isBackupMode(driveRefs)

    def getDrives(self):
        return self.buManager.getBackupDrives() + self.buManager.getContentDrives()

    # noinspection PyUnusedLocal
    def toggleLock(self, evt):
        if self.running:
            return
        self.running = True
        driveRefs = self.drives.GetSelectedData()
        if not driveRefs:
            self.running = False
            return
        self.buManager.toggleLock(driveRefs)
        self.refreshDrives()
        self.running = False

    def createMenu(self):
        menu = wx.Menu()
        driveRefs = self.drives.GetSelectedData()
        editDir = None
        remoteIndex = None
        lock = None
        about = None
        if len(driveRefs) == 1:
            d = driveRefs[0]
            if not self.buManager.isBackupMode(driveRefs[0:1]):
                disp = wx.MenuItem(menu, -1, 'Content Drive - %s' % d[AEnums.NAME])
                disp.SetBitmap(self.tb_drive)
                disp.Enable(False)
                menu.AppendItem(disp)
                menu.AppendSeparator()
                if not self.guest:
                    editDir = wx.MenuItem(menu, self.ids['popupdir'], 'Manage Directories', 'Specify Dirs For Drive')
                    editDir.SetBitmap(self.tb_dirs)
                    menu.AppendItem(editDir)
            else:
                disp = wx.MenuItem(menu, -1, 'Backup Drive - %s' % d[AEnums.NAME], d[AEnums.NAME])
                disp.SetBitmap(self.tb_drive)
                disp.Enable(False)
                menu.AppendItem(disp)
            about = wx.MenuItem(menu, self.ids['popupabout'], 'About - %s' % d[AEnums.NAME], 'Display Drive Info Dialog')
            about.SetBitmap(self.tb_driveinfo)
            menu.AppendItem(about)
            if not self.guest:
                menu.AppendSeparator()
        if not self.guest:
            if self.isBackupMode():
                allLocked = True
                for driveRef in driveRefs:
                    if not self.buManager.isLocked(driveRef):
                        allLocked = False
                        break
                if allLocked:
                    lock = wx.MenuItem(menu, self.ids['popuplock'], 'Unlock', 'Allow File Operations')
                else:
                    lock = wx.MenuItem(menu, self.ids['popuplock'], 'Lock', 'Disallow File Operations')
                lock.SetBitmap(self.tb_lock_small)
                menu.AppendItem(lock)
            elif self.isContentMode():
                remoteIndex = wx.MenuItem(menu, self.ids['popupremote'], 'Remote Path', 'Edit Remote Info')
                remoteIndex.SetBitmap(self.tb_remote)
                menu.AppendItem(remoteIndex)
            chkdsk = wx.MenuItem(menu, self.ids['popupchkdsk'], 'Chkdsk', 'Chkdsk Drive(s)')
            chkdsk.SetBitmap(self.tb_chkdsk)
            remove = wx.MenuItem(menu, self.ids['popupremove'], 'Remove', 'Remove Drive(s)')
            remove.SetBitmap(self.tb_remove_small)
            refresh = wx.MenuItem(menu, self.ids['popuprefresh'], 'Refresh', 'Refresh Drive(s)')
            refresh.SetBitmap(self.tb_refresh_small)
            menu.AppendItem(remove)
            menu.AppendItem(refresh)
            menu.AppendItem(chkdsk)
            self.Bind(wx.EVT_MENU, self.guiRemoveDrive, remove)
            self.Bind(wx.EVT_MENU, self.refreshSelected, refresh)
            self.Bind(wx.EVT_MENU, self.chkdskSelected, chkdsk)
        if about:
            menu.Bind(wx.EVT_MENU, self.showDriveInfo, about)
        if editDir:
            menu.Bind(wx.EVT_MENU, self.editDirs, editDir)
        if lock:
            menu.Bind(wx.EVT_MENU, self.toggleLock, lock)
        elif remoteIndex:
            menu.Bind(wx.EVT_MENU, self.remoteIndex, remoteIndex)
        return menu

    def popupHighlight(self, evt):
        mid = evt.GetMenuId()
        item = None
        if self.menu:
            item = self.menu.FindItemById(mid)
        item = item or self.GetMenuBar().FindItemById(mid)
        if item:
            self.SetStatusText(item.GetHelp(), 0)

    def popupDone(self, evt):
        self.SetStatusText('', 0)

    # noinspection PyUnusedLocal
    def showDriveInfo(self, evt):
        if self.running:
            return
        driveRefs = self.drives.GetSelectedData()
        if not driveRefs:
            return
        DriveInfo(self, driveRefs[0]).ShowModal()

    # noinspection PyUnusedLocal
    def editDirs(self, evt):
        if self.running:
            return
        driveRefs = self.drives.GetSelectedData()
        if len(driveRefs) > 1:
            dialog = wx.MessageBox(self, "Multiple Selections",
                                   "You can only select one drive at a time for remote indexing!", style=wx.OK)
            return
        if not driveRefs:
            return
        driveRef = driveRefs[0]
        dialog = manageDirDialog(self, driveRef)
        dialog.ShowModal()

    # noinspection PyUnusedLocal
    def toggleDrives(self, event):
        if self.running:
            return
        else:
            self.running = True
        driveRefs = self.drives.GetSelectedData()
        if not driveRefs:
            return
        for driveRef in driveRefs:
            self.buManager.toggleDrive(driveRef)
        self.refreshDrives()
        self.running = False

    # noinspection PyUnusedLocal
    def remoteIndex(self, event):
        if self.running:
            return
        driveRefs = self.drives.GetSelectedData()
        if len(driveRefs) > 1:
            wx.MessageBox(self, "Multiple Selections",
                          "You can only select one drive at a time for remote indexing!", style=wx.OK)
            return
        if not driveRefs:
            return
        driveRef = driveRefs[0]
        remoteDialog(self, driveRef).ShowModal()

    # noinspection PyUnusedLocal
    def threadError(self, event=None):
        if self.running:
            self.idle()
        if self.thread:
            self.thread.join()
        try:
            title, exc_trace = self.bucket.get(block=False)
        except realQueue.Empty:
            pass
        else:
            # deal with the exception
            exc_trace = str(exc_trace)
            self.results.setMode(easyListCtrl.STRING)
            self.resultPage.Clear()
            self.results.populateStrings([exc_trace])
            wx.MessageDialog(self, exc_trace, title, wx.OK | wx.ICON_ERROR).ShowModal()
            self.SetStatusText('Error!', 1)
            self.logger.error(exc_trace)

    # noinspection PyUnusedLocal
    def search(self, event):
        if self.running:
            return
        if self.buManager.getSetName() not in self.sdialog:
            self.sdialog[self.buManager.getSetName()] = SearchDialog(self)
        d = self.sdialog[self.buManager.getSetName()]
        if d.ShowModal() == wx.ID_CANCEL:
            return
        else:
            regex = d.GetValue()
        self.runInThread("Searching for " + regex[AEnums.QUERY] + "...", 'Searching',
                         self.threadedSearch, fileType=False, kill=False, args=(regex,))

    def clear(self, event):
        self.results.DeleteAllItems()
        self.resultPage.Hide()
        self.resultPanel.Layout()
        self.typeChanged(event)
        self.SetStatusText('', 0)
        self.SetStatusText('', 1)
        self.updateTreeLabel('selected drive')

    def resultPageChanged(self, event):
        results = event.GetClientData()
        self.results.setMode(easyListCtrl.FILE)
        self.results.populateFiles(results, None, True)
        newItem = wx.ListItem()
        newItem.SetText("Over " + str(self._maxResults) + " search results.")
        newItem.SetTextColour(wx.RED)
        newItem.SetImage(self.fileUtilObj.getError())
        self.results.InsertItem(newItem)
        self.results.adjust()

    def paginateResults(self, results):
        lowerBound = 0
        upperBound = self._maxResults - 1
        self.resultPage.Clear()
        truncate = False
        while not truncate:
            if upperBound > len(results):
                upperBound = len(results) - 1
                truncate = True
            string = str(lowerBound + 1) + "-" + str(upperBound + 1)
            resultArray = results[lowerBound:upperBound]
            self.resultPage.Append(string, resultArray)
            lowerBound = upperBound + 1
            upperBound += self._maxResults
            upperBound += 1
        self.resultPage.SetStringSelection("1-" + str(self._maxResults))

    def searchDone(self, files):
        dirs = [x for x in files if x[AEnums.DIR]]
        filesOnly = [x for x in files if not x[AEnums.DIR]]
        total = self.fileResults(filesOnly + dirs)
        self.SetStatusText("Search complete! " + str(total) + " Result(s) found.", 1)
        self.updateResultLabel("Search Results")

    def threadedSearch(self, regex):
        results = self.buManager.searchFiles(regex)
        wx.CallAfter(self.searchDone, results)

    # noinspection PyUnusedLocal
    def refreshAll(self, event):
        drives = self.buManager.getBackupDrives() + self.buManager.getContentDrives()
        self.runInThread("Refreshing All Drives", "Refreshing All Drives",
                         self.threadedRefresh, fileType=False, kill=True, args=(drives,))

    # noinspection PyUnusedLocal
    def refreshBackup(self, event):
        drives = self.buManager.getBackupDrives()
        self.runInThread("Refreshing Backup Drives", "Refreshing Backup Drives",
                         self.threadedRefresh, fileType=False, kill=True, args=(drives,))

    # noinspection PyUnusedLocal
    def refreshContent(self, event):
        drives = self.buManager.getContentDrives()
        self.runInThread("Refreshing Content Drives", "Refreshing Content Drives",
                         self.threadedRefresh, fileType=False, kill=True, args=(drives,))

    # noinspection PyUnusedLocal
    def chkdskSelected(self, event):
        if self.running:
            return
        driveRefs = self.drives.GetSelectedData()
        if not driveRefs:
            return
        self.runInThread('Running chkdsk on drive(s)...', 'Checking drive(s)...',
                         self.threadedChkdsk, fileType=None, kill=True, fileDisp=False,
                         args=(driveRefs,))

    def threadedChkdsk(self, driveRefs, kill=None):
        errorMessages = self.buManager.chkdsk(driveRefs, kill)
        wx.CallAfter(self.chkdskDone, errorMessages)

    def chkdskDone(self, errorMessages):
        self.thread.join()
        self.idle()
        self.results.setMode(easyListCtrl.STRING)
        self.resultPage.Clear()
        self.results.populateStrings(errorMessages)
        if errorMessages:
            self.SetStatusText("Chkdsk completed with errors.", 1)
        else:
            self.SetStatusText("Chkdsk completed.", 1)
        self.updateResultLabel("Chkdsk Errors")
        self.typeChanged(None)
        self.chkDskNag()

    # noinspection PyUnusedLocal
    def refreshSelected(self, event):
        driveRefs = self.drives.GetSelectedData()
        if driveRefs is None:
            wx.MessageDialog(self, 'Must select at least one drive to refresh!', 'Error',
                             wx.OK | wx.ICON_ERROR).ShowModal()
            return
        remote = False
        for driveRef in driveRefs:
            if self.buManager.remoteReady(driveRef):
                answer = wx.MessageDialog(self, 'Do you want to remote index eligible drives?', 'Remote Index?',
                                          style=wx.YES_NO | wx.ICON_QUESTION).ShowModal()
                if answer == wx.ID_YES:
                    remote = True
                break
        self.runInThread("Refreshing drive(s)...", "Refreshing drive(s)...",
                         self.threadedRefresh, fileType=False, kill=True, args=(driveRefs,), kwargs={'remote': remote})

    def threadedRefresh(self, drives, remote=False, kill=None):
        errorMessages = self.buManager.refresh(drives, remote, kill=kill)
        wx.CallAfter(self.refreshDone, errorMessages)

    def refreshDone(self, errorMessages):
        print 'done'
        self.thread.join()
        self.idle()
        self.results.setMode(easyListCtrl.STRING)
        self.resultPage.Clear()
        self.results.populateStrings(errorMessages)
        if errorMessages:
            self.SetStatusText("Refresh completed with errors.", 1)
        else:
            self.SetStatusText("Refresh completed.", 1)
        self.updateResultLabel("Refresh Errors")
        self.typeChanged(None)

    # noinspection PyUnusedLocal
    def editLists(self, event):
        if self.running:
            return
        dialog = listdialog(self, self.buManager)
        dialog.ShowModal()

    # noinspection PyUnusedLocal
    def exit(self, event):
        if self.running:
            answer = wx.MessageBox("A background action is still self.running, quit anyway?", "Potential data loss!",
                                   wx.OK | wx.CANCEL, self)
            if answer == wx.OK:
                os.sys.exit(0)
            else:
                return
        else:
            os.sys.exit(0)

    def busy(self, title, kill, fileType=False, fileDisp=True):
        if not title:
            title = "Working..."
        self.running = True
        self.cursor = wx.BusyCursor()
        if fileType:
            self.progress = FileProgressDialog(self, title, kill)
        elif fileType is None:
            self.progress = ChkdskDialog(self, title, kill)
        else:
            self.progress = ScrollingProgressDialog(self, title, kill, fileDisp)
        self.progress.ShowModal()

    def idle(self):
        if self.progress is not None:
            self.progress.Close(1)
        while not self.errorAnswerQueue.empty():
            self.errorAnswerQueue.get_nowait()
        self.running = False
        self.cursor = None

    def backupDone(self, errorMessages):
        self.thread.join()
        self.idle()
        self.results.setMode(easyListCtrl.STRING)
        self.resultPage.Clear()
        self.results.populateStrings(errorMessages)
        if errorMessages:
            self.SetStatusText("Backup completed with errors.", 1)
        else:
            self.SetStatusText("Backup completed.", 1)
        self.updateResultLabel("Backup Errors")
        self.typeChanged(None)

    def restoreDone(self, errorMessages):
        self.thread.join()
        self.idle()
        self.results.setMode(easyListCtrl.STRING)
        self.resultPage.Clear()
        self.results.populateStrings(errorMessages)
        if errorMessages:
            self.SetStatusText("Restore completed with errors.", 1)
        else:
            self.SetStatusText("Restore completed.", 1)
        self.updateResultLabel("Restore Errors")
        self.typeChanged(None)

    def backupProgress(self, status):
        self.SetStatusText(status, 1)

    def restoreProgress(self, status):
        self.SetStatusText(status, 1)

    def missingDrive(self, driveID):
        return wx.MessageDialog(
            self.progress or self, "Please connect drive " + driveID, "Hit cancel to skip this drive.",
            wx.OK | wx.CANCEL).ShowModal() == wx.ID_OK

    def runInThread(self, status, title, target, fileType=False, kill=True, fileDisp=True, args=None, kwargs=None):
        if self.running:
            return
        self.SetStatusText(status, 1)
        args = args or []
        kwargs = kwargs or {}
        killEvent = None
        if kill:
            killEvent = Event()
            kwargs['kill'] = killEvent
        wx.CallAfter(self.busy, title, killEvent, fileType, fileDisp)
        self.thread = AnyThread(title=title, bucket=self.bucket, target=functools.partial(target, *args, **kwargs))
        self.thread.start()

    # noinspection PyUnusedLocal
    def guiToBackup(self, event):
        self.runInThread("Generating list of files to backup...", 'Generating Backup List',
                         self.threadedNewFiles, fileType=False, kill=False, fileDisp=False)

    # noinspection PyUnusedLocal
    def guiDuplicates(self, event):
        self.runInThread("Generating list of pending duplicates...", "Generating Pending Duplicate List",
                         self.threadedDuplicateFiles, fileType=False, kill=False, fileDisp=False)

    # noinspection PyUnusedLocal
    def guiOldFiles(self, event):
        self.runInThread("Generating list of old files...", "Generating Old File List",
                         self.threadedOldFiles, fileType=False, kill=False, fileDisp=False)

    def toBackupDone(self, files):
        total = self.fileResults(files)
        self.updateResultLabel("Files To Backup")
        self.SetStatusText("Backup list generated -- " + str(total) + " files found.", 1)

    def fileResults(self, files):
        self.idle()
        self.thread.join()
        self.results.setMode(easyListCtrl.FILE)
        self.resultPage.Clear()
        total = len(files)
        tooBig = False
        if len(files) > self._maxResults:
            tooBig = True
            self.paginateResults(files)
            files = files[0:self._maxResults]
        self.results.populateFiles(files, None, True)
        if tooBig:
            self.resultPage.Show()
            newItem = wx.ListItem()
            newItem.SetText("Over " + str(self._maxResults) + " files found, clipping results.")
            newItem.SetTextColour(wx.RED)
            newItem.SetImage(self.fileUtilObj.getError())
            self.results.InsertItem(newItem)
        else:
            self.resultPage.Hide()
        self.resultPanel.Layout()
        self.results.adjust()
        return total

    def duplicatesDone(self, files):
        total = self.fileResults(files)
        self.updateResultLabel("Duplicate Files")
        self.SetStatusText("Duplicate file list generated -- " + str(total) + " files found.", 1)

    def oldFilesDone(self, files):
        total = self.fileResults(files)
        self.updateResultLabel("Old Files")
        self.SetStatusText("Old file list generated -- " + str(total) + " files found.", 1)

    def threadedOldFiles(self):
        files = self.buManager.getOldFiles()
        wx.CallAfter(self.oldFilesDone, files)

    def threadedDuplicateFiles(self):
        files = self.buManager.getDuplicates()
        wx.CallAfter(self.duplicatesDone, files)

    def threadedNewFiles(self):
        files = self.buManager.getNewFiles()
        wx.CallAfter(self.toBackupDone, files)

    # noinspection PyUnusedLocal
    def guiBackup(self, event):
        files = self.buManager.getOldFiles()
        if files:
            answer = wx.MessageDialog(self,
                                      'This will delete {0} old files, proceed? Review via CTRL+O'.format(len(files)),
                                      'Backup Now?', style=wx.YES_NO | wx.ICON_QUESTION).ShowModal()
            if answer != wx.ID_YES:
                return
        self.runInThread("Backing up new files...", "Backing Up New Files",
                         self.threadedBackup, fileType=True, kill=True)

    # noinspection PyUnusedLocal
    def guiRestore(self, event):
        self.runInThread("Restoring missing files...", "Restoring Missing Files",
                         self.threadedRestore, fileType=True, kill=True)

    def threadedRestore(self, kill=None):
        errorMessages = self.buManager.restoreOldFiles(kill=kill)
        wx.CallAfter(self.restoreDone, errorMessages)

    def threadedBackup(self, kill=None):
        errorMessages = self.buManager.backupNewFiles(kill=kill)
        wx.CallAfter(self.backupDone, errorMessages)

    def guiAddBackupDrive(self, event):
        self.guiAddDrive(event, AEnums.BACKUP)

    def guiAddContentDrive(self, event):
        self.guiAddDrive(event, AEnums.CONTENT)

    # noinspection PyUnusedLocal
    def guiAddDrive(self, event, driveType=None):
        if not driveType:
            driveType = AEnums.BACKUP
        if self.running:
            return
        drives = DriveUtil.getLetters()
        lst = drives.keys()
        if driveType == AEnums.BACKUP:
            dialog = wx.MultiChoiceDialog(self, "Choose a drive", "Choose", lst)
            if dialog.ShowModal() != wx.ID_OK:
                return
            choices = dialog.GetSelections()
            if not choices:
                return
            strings = []
            for choice in choices:
                strings.append(drives[lst[choice]])
        else:
            dialog = MultiDirDialog(self, 'Choose directories to add for content drives.', 'Choose content directories')
            if dialog.ShowModal() != wx.ID_OK:
                return
            choices = dialog.GetPaths()
            if not choices:
                return
            strings = []
            for choice in choices:
                strings.append(choice.strip(os.sep))
        indexNow = False
        answer = wx.MessageDialog(self, 'Index drive(s) now?', 'OK to index, cancel to defer',
                                  style=wx.YES_NO | wx.ICON_QUESTION).ShowModal()
        if answer == wx.ID_YES:
            indexNow = True
        if self.buManager.getSetName() in self.sdialog:
            del self.sdialog[self.buManager.getSetName()]
        self.runInThread("Adding and indexing drive(s)...", "Adding %d drives..." % len(strings),
                         self.threadedAdd, fileType=False, kill=True, args=(strings, indexNow, driveType))

    # noinspection PyUnusedLocal
    def guiRemoveDrive(self, event):
        if self.running:
            return
        driveRefs = self.drives.GetSelectedData()
        if not driveRefs:
            return

        self.runInThread('Removing drive(s)...', 'Removing %d drives..' % len(driveRefs),
                         self.threadedRemove, fileType=False, kill=False, fileDisp=False, args=(driveRefs,))

    def threadedRemove(self, driveRefs):
        self.buManager.removeDrives(driveRefs)
        wx.CallAfter(self.removeDone)

    def removeDone(self):
        self.thread.join()
        self.idle()
        self.drives.DeleteAllItems()
        self.contents.DeleteAllItems()
        self.dirTreeObj.DeleteAllItems()
        if self.buManager.getSetName() in self.sdialog:
            del self.sdialog[self.buManager.getSetName()]
        drives = self.getDrives()
        self.updateFileLabel("")
        if drives:
            self.drives.populateDrives(drives)

    def threadedAdd(self, strings, index, driveType, kill=None):
        errorMessages = []
        if driveType == AEnums.BACKUP:
            for letter in strings:
                result = self.buManager.addBackupDrive(letter, index, kill=kill)
                if result == AEnums.DUPLICATE:
                    errorMessages.append("Drive at " + letter + ": has already been added!")
                elif result == AEnums.UNNAMED:
                    errorMessages.append(
                        "Drive at " + letter +
                        ": is unnamed, you must define a volume name before adding a drive to AnyBackup!")
        else:
            drives = {}
            for dirName in strings:
                (letter, path) = DriveUtil.explodePath(dirName)
                if not letter in drives:
                    drives[letter] = []
                drives[letter].append(path)
            for letter in drives.keys():
                result = self.buManager.addContentDrive(letter, drives[letter], index, kill=kill)
                if result == AEnums.UNNAMED:
                    wx.MessageDialog(None, 'Drives must have a volume name to be added!', 'Unnamed Drive',
                                     wx.OK | wx.ICON_ERROR).ShowModal()
        wx.CallAfter(self.done, errorMessages)

    # noinspection PyUnusedLocal
    def exportCsv(self, event=None):
        files = self.results.mapping.values()
        if not files:
            return
        dialog = wx.FileDialog(self, 'Save to file', 'Export CSV', wildcard='CSV files(*.csv)', style=wx.FD_SAVE)
        if dialog.ShowModal() != wx.ID_OK:
            return
        f = dialog.GetPath()
        if not f:
            return
        with open(f, 'wb') as csvfile:
            csvwriter = csv.writer(csvfile, delimiter=',', quotechar="'", quoting=csv.QUOTE_NONNUMERIC)
            if self.results.mode == self.results.FILE:
                csvwriter.writerow(['File', 'Size', 'Last Modified Time'])
                for fileRef in files:
                    csvwriter.writerow(
                        [(unicode(fileRef[AEnums.PREFIX] + os.sep + fileRef[AEnums.PATH] + os.sep +
                         fileRef[AEnums.NAME]).encode('utf-8')).replace(',', ''),
                         fileRef[AEnums.SIZE], fileRef[AEnums.MTIME]])
            elif self.results.mode == self.results.STRING:
                csvwriter.writerow(['Message'])
                for msg in files:
                    csvwriter.writerow([msg])

    # noinspection PyUnusedLocal
    def copyTo(self, event=None):
        if self.running:
            return
        files = self.results.GetSelectedData()
        if not files:
            return
        dialog = wx.DirDialog(self, 'Choose a destination to copy to', 'Copy File(s)')
        if dialog.ShowModal() != wx.ID_OK:
            return
        path = dialog.GetPath()
        if not path:
            return
        if not os.access(path, os.W_OK):
            dialog = wx.MessageDialog(self, 'Cannot write to path: {0}'.format(path), 'Error', style=wx.ICON_ERROR)
            dialog.ShowModal()
            return
        self.runInThread("Copying File(s)...", "Copying File(s)...",
                         self.threadedCopy, fileType=True, kill=True, args=(files, path))

    def threadedCopy(self, files, path, kill=None):
        errors = self.buManager.copyFilesTo(files, path, kill=kill)
        wx.CallAfter(self.copyDone, errors)

    def copyDone(self, errorMessages):
        self.thread.join()
        self.idle()
        if errorMessages:
            self.results.setMode(easyListCtrl.STRING)
            self.resultPage.Clear()
            self.results.populateStrings(errorMessages)
            self.updateResultLabel("Copy Errors")
            self.typeChanged(None)
        if errorMessages:
            self.SetStatusText("Copy completed with errors.", 1)
        else:
            self.SetStatusText("Copy completed.", 1)

    # noinspection PyUnusedLocal
    def resultFileClicked(self, event):
        if self.running:
            return
        if self.results.isAutoSelect():
            return
        fileRef = self.results.GetSelectedData()
        if not fileRef:
            return
        else:
            fileRef = fileRef[0]
        if not type(fileRef) == dict:
            return
        if not fileRef[AEnums.DIR] and not AEnums.PARENT in fileRef:
            return
        driveRef = self.buManager.getDrive(fileRef)
        if not driveRef:
            return
        self.dirTreeObj.populate(driveRef)
        if fileRef[AEnums.DIR]:
            self.updateFileLabel(fileRef[AEnums.PREFIX] + os.sep + fileRef[AEnums.PATH] + os.sep + fileRef[AEnums.NAME])
            parent = fileRef.get(AEnums.PARENT, None)
            files = self.buManager.getFiles(fileRef[AEnums.ID])
        else:
            self.updateFileLabel(fileRef[AEnums.PREFIX] + os.sep + fileRef[AEnums.PATH])
            parent = fileRef.get(AEnums.PARENT, None)
            files = self.buManager.getFiles(parent)
        files = self.buManager.sortFiles(files)
        files.reverse()
        self.contents.DeleteAllItems()
        if not fileRef[AEnums.DIR]:
            p = self.buManager.getFile(parent)
            self.contents.populateFiles(files, p)
        else:
            self.contents.populateFiles(files, fileRef)

    def fileClicked(self, event):
        if self.running:
            return
        if self.contents.isAutoSelect():
            return
        fileRef = self.contents.GetData(event.GetItem())
        if fileRef.get(AEnums.DIR, None) or AEnums.TYPE in fileRef:
            self.contents.DeleteAllItems()
            files = self.buManager.getFiles(fileRef)
            files = self.buManager.sortFiles(files)
            try:
                self.updateFileLabel(fileRef[AEnums.PREFIX] + os.sep + fileRef[AEnums.PATH] +
                                     os.sep + fileRef[AEnums.NAME])
            except KeyError:
                d = fileRef
                self.updateFileLabel(d[AEnums.NAME] + ":" + os.sep)
            files.reverse()
            self.contents.populateFiles(files, fileRef)
        elif fileRef:
            self.contentShowInExplorer(event)

    def done(self, errorMessages):
        self.idle()
        self.results.setMode(easyListCtrl.STRING)
        self.resultPage.Clear()
        self.results.populateStrings(errorMessages)
        self.refreshDrives()
        self.updateFileLabel("Files")
        self.SetStatusText("", 1)
        self.unsaved = True

    def refreshDrives(self):
        drives = self.getDrives()
        self.drives.DeleteAllItems()
        self.contents.DeleteAllItems()
        self.results.DeleteAllItems()
        self.dirTreeObj.DeleteAllItems()
        self.SetStatusText("", 1)
        self.drives.populateDrives(drives)

    # noinspection PyUnusedLocal
    def typeChanged(self, event):
        if self.running:
            return
        drives = self.getDrives()
        self.dirTreeObj.DeleteAllItems()
        self.drives.DeleteAllItems()
        self.contents.DeleteAllItems()
        self.updateFileLabel("Files")
        self.drives.populateDrives(drives)

    def treeExpanded(self, event):
        item = event.GetItem()
        fileRef = self.dirTreeObj.GetItemData(item).GetData()
        files = self.buManager.getFiles(fileRef)
        files = self.buManager.sortFiles(files)
        dirs = [x for x in files if x[AEnums.DIR]]
        if dirs and self.dirTreeObj.GetChildrenCount(item) <= 0:
            self.dirTreeObj.populateTree(item, dirs)
            self.dirTreeObj.Expand(item)
        elif not dirs:
            self.dirTreeObj.SetItemHasChildren(item, False)

    def updateFileLabel(self, string):
        paneInfo = self.mgr.GetPane(self.contents.getListCtrl())
        paneInfo.Caption(unicode(string).encode('utf-8'))
        self.mgr.Update()

    def updateResultLabel(self, string):
        paneInfo = self.mgr.GetPane('Results')
        paneInfo.Caption(string)
        self.mgr.Update()

    def updateTreeLabel(self, string):
        paneInfo = self.mgr.GetPane('Directory View')
        paneInfo.Caption('Directory view of %s' % string)
        self.mgr.Update()

    def treeClicked(self, event):
        item = event.GetItem()
        fileRef = self.dirTreeObj.GetItemData(item).GetData()
        self.contents.DeleteAllItems()
        files = self.buManager.getFiles(fileRef)
        files = self.buManager.sortFiles(files)
        dirs = [x for x in files if x[AEnums.DIR]]
        if dirs and self.dirTreeObj.GetChildrenCount(item) <= 0:
            self.dirTreeObj.populateTree(item, dirs)
            self.dirTreeObj.Expand(item)
        elif not dirs:
            self.dirTreeObj.SetItemHasChildren(item, False)
        try:
            self.updateFileLabel(fileRef[AEnums.PREFIX] + os.sep + fileRef[AEnums.PATH] + os.sep + fileRef[AEnums.NAME])
        except KeyError:
            d = fileRef
            self.updateFileLabel(d[AEnums.NAME] + ":" + os.sep)
        files.reverse()
        self.contents.populateFiles(files, fileRef)

    def driveClicked(self, event):
        if self.running:
            return
        if self.drives.isAutoSelect():
            return
        driveRef = self.drives.GetData(event.GetItem())
        if not driveRef:
            return
        self.dirTreeObj.populate(driveRef)
        self.updateTreeLabel(driveRef[AEnums.NAME])
        self.contents.DeleteAllItems()
        rootFile = self.buManager.getRootFile(driveRef)
        if not rootFile:
            return
        files = self.buManager.sortFiles(rootFile)
        self.updateFileLabel(driveRef[AEnums.NAME] + ":\\")
        files.reverse()
        self.contents.populateFiles(files, driveRef)

    def chkDskNag(self):
        drives = self.buManager.toChkdsk()
        if not drives:
            return
        msg = 'You have {0} drive(s) which have not had chkdsk run in at least {1} month(s)'.format(
            len(drives), self.buManager.nagLength)
        self.SetStatusText(msg, 1)
        toaster = TB.ToasterBox(self, tbstyle=TB.TB_SIMPLE)
        toaster.SetPopupPauseTime(10000)
        toaster.SetTitle('Chkdsk Nag')
        toaster.SetPopupText(msg)
        toaster.Play()
